This notebook summarizes the findings from assigning cell type labels
to all ScPCA samples using SCimilarity.
Running this notebook requires the results from the
cell-type-consensus and cell-type-scimilarity
module in OpenScPCA-nf to be saved to
OpenScPCA-analysis/data/current/results.
suppressPackageStartupMessages({
# load required packages
library(ggplot2)
})
# Set default ggplot theme
theme_set(
theme_classic()
)
# Define color ramp for shared use in the heatmaps
heatmap_col_fun <- circlize::colorRamp2(c(0, 1), colors = c("white", "darkslateblue"))
# Set heatmap padding option
ComplexHeatmap::ht_opt(TITLE_PADDING = grid::unit(0.6, "in"))
# settings
options(
dplyr.summarise.inform = FALSE,
readr.show_col_types = FALSE
)
Functions
# function to read in both scimilarity and consensus results and combine into a single data frame
prep_data <- function(library_id, scimilarity_file, consensus_file){
scimilarity_df <- readr::read_tsv(scimilarity_file)
consensus_df <- readr::read_tsv(consensus_file)
annotation_df <- consensus_df |>
dplyr::left_join(scimilarity_df, by = c("barcodes" = "barcode")) |>
dplyr::mutate(
cell_id = glue::glue("{library_id}-{barcodes}"),
total_cells_per_library = dplyr::n()
)
return(annotation_df)
}
# Copied from scpca-nf template report
#' Format DT datatables
#'
#' @param df Data frame to format
#'
#' @return DT datatable
format_datatable <- function(df, caption) {
df |>
DT::datatable(
rownames = FALSE,
extensions = "Scroller",
caption = caption,
options = list(
scroller = TRUE,
deferRender = TRUE,
scrollX = TRUE,
scrollY = 400,
scrollCollapse = TRUE,
language = list(search = "Filter:"),
columnDefs = list(list(
# render missing values as "NA" rather than blanks in the table
targets = "_all",
render = DT::JS(
"function(data, type, row, meta) {",
"return data === null ? 'NA' : data;",
"}"
)
))
)
)
}
Data setup
# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)
# results directory with cell-type-consensus
results_dir <- file.path(repository_base, "data", "current", "results")
consensus_results_dir <- file.path(results_dir, "cell-type-consensus")
scimilarity_results_dir <- file.path(results_dir, "cell-type-scimilarity")
# diagnoses table used for labeling plots from cell-type-consensus module
diagnoses_file <- file.path(repository_base, "analyses", "cell-type-consensus", "sample-info", "project-diagnoses.tsv")
# use the jaccard functions from cell-type-neuroblastoma-04 module
jaccard_functions <- file.path(repository_base, "analyses", "cell-type-neuroblastoma-04", "scripts", "utils", "jaccard-utils.R")
source(jaccard_functions)
# list all results files
consensus_results_files <- list.files(consensus_results_dir, pattern = "_processed_consensus-cell-types\\.tsv.\\gz$", recursive = TRUE, full.names = TRUE)
consensus_files_df <- data.frame(
library_id = stringr::word(basename(consensus_results_files), 1, sep = "_"),
consensus_file = consensus_results_files
)
scimilarity_results_files <- list.files(scimilarity_results_dir, pattern = "_processed_scimilarity-celltype-assignments\\.tsv.\\gz$", recursive = TRUE, full.names = TRUE)
scimilarity_files_df <- data.frame(
library_id = stringr::word(basename(scimilarity_results_files), 1, sep = "_"),
scimilarity_file = scimilarity_results_files
)
all_files_df <- scimilarity_files_df |>
dplyr::left_join(consensus_files_df, by = "library_id")
# define cell line projects to remove
cell_line_projects <- c("SCPCP000020", "SCPCP000024")
# check that everything has a matching consensus file
# we joined the consensus into the scimilarity so only libraries with scimilarity are included
if(sum(is.na(all_files_df$consensus_file)) > 0){
stop("Missing matching consensus cell type file for all libraries")
}
# read in diagnoses
diagnoses_df <- readr::read_tsv(diagnoses_file)
# read in all results as a single dataframe
results_df <- all_files_df |>
purrr::pmap(prep_data) |>
dplyr::bind_rows() |>
# remove cell line projects
dplyr::filter(!project_id %in% cell_line_projects) |>
# add in diagnoses
dplyr::left_join(diagnoses_df, by = "project_id") |>
dplyr::mutate(
# create a label for plotting
project_label = glue::glue("{project_id}:{diagnosis}")
)
SCimilarity results
Below we will summarize the results from SCimilarity on
all ScPCA projects.
Table of all cell types in ScPCA
First, we just look at the cell types identified and see if any of
the cells are not assigned.
sum(is.na(results_df$scimilarity_celltype_annotation))
[1] 0
It looks like every cell gets a cell type, so that’s something to
keep in mind when looking at these results.
Let’s see what cell types are presented in our data?
results_df |>
dplyr::ungroup() |>
dplyr::count(scimilarity_celltype_annotation, name = "total_cells") |>
dplyr::arrange(desc(total_cells)) |>
format_datatable(caption = "All samples")
So it looks like we have a lot of “native cell” labels which really
just means the cell is a cell and didn’t align with anything else in the
reference. This label is probably similar to our “other” label in
CellAssign.
Also, most of our cells are progenitor cells or hematopoietic stem
cells, which either means a lot of the cells are tumor cells and show
stem and progenitor like properties or these are from the blood tumors
which make up a large part of our atlas. Either way, we have cell
types!
Table of cell types per project
This section shows a table of all cell types identified in each
project. Each project will have its own table.
# get all the unique labels
project_labels <- unique(results_df$project_label)
tables <- project_labels |>
purrr::map(\(label) {
results_df |>
dplyr::filter(project_label == label) |>
dplyr::group_by(scimilarity_celltype_annotation) |>
dplyr::summarise(
total_cells = dplyr::n()
) |>
dplyr::arrange(desc(total_cells)) |>
format_datatable(caption = label)
})
htmltools::tagList(tables)
Comparison to existing consensus cell types
In this section, we compare the top 15 SCimilarity cell
type annotations to the consensus cell types determined using only
CellAssign and SingleR annotations for all
libraries in a single project. The rows correspond to consensus cell
types and the columns correspond to SCimilarity
annotations. All other cell types not in the top 10 represented cell
types in a project are grouped into the “All remaining cell types”
category.
results_df <- results_df |>
dplyr::group_by(project_id) |>
dplyr::mutate(
# get most frequently observed cell types across libraries in that project
scimilarity_celltype_lumped = forcats::fct_lump_n(scimilarity_celltype_annotation, 15, other_level = "All remaining cell types", ties.method = "first") |>
# sort by frequency
forcats::fct_infreq() |>
# make sure all remaining and unknown are last, use this to assign colors in specific order
forcats::fct_relevel("All remaining cell types", after = Inf) |>
as.character()
)
project_labels |>
purrr::walk(\(label){
results_df |>
dplyr::filter(project_label == label) |>
make_jaccard_heatmap(
annotation_col1 = "scimilarity_celltype_lumped",
annotation_col2 = "consensus_annotation",
label1 = glue::glue("{label} \nSCimilarity"),
label2 = "Consensus"
)
})





















As expected there is some direct agreement or places where more broad
consensus cell types are split up across more granular cell types in
SCimilarity. A few notable examples:
SCPCP000001: Cells labeled as macrophages in consensus
are split between glial and microglial cells in
SCimilarity. Natural killer cells match up between
consensus and SCimilarity.
SCPCP000006: Endothelial cells match up between
consensus and SCimilarity. The unknown cells in consensus
correspond to mesenchymal stem cells and a variety of kidney cells.
SCPCP000007: Mature T cell in consensus is split
between CD4 and CD8 T cells in SCimilarity.
We also see places where there is not agreement, which is to be
expected.
Conclusions
Of the 203 possible cell types in SCimilarity, 191 of
them are represented in ScPCA samples. There are quite a few scenarios
where consensus cell types “agree” with SCimilarity. It
will be informative to look at the new consensus cell types and how they
change with the addition of the SCimilarity annotations,
but in general the addition of SCimilarity seems quite
useful!
Session info
# record the versions of the packages used in this analysis and other environment information
sessionInfo()
R version 4.4.2 (2024-10-31)
Platform: aarch64-apple-darwin20
Running under: macOS Sequoia 15.6.1
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/Chicago
tzcode source: internal
attached base packages:
[1] stats graphics grDevices datasets utils methods base
other attached packages:
[1] ggplot2_3.5.2
loaded via a namespace (and not attached):
[1] tidyr_1.3.1 sass_0.4.10 generics_0.1.4 renv_1.1.4 shape_1.4.6.1 stringi_1.8.7 hms_1.1.3 digest_0.6.37 magrittr_2.0.3 evaluate_1.0.5
[11] grid_4.4.2 RColorBrewer_1.1-3 iterators_1.0.14 circlize_0.4.16 fastmap_1.2.0 rprojroot_2.1.0 foreach_1.5.2 doParallel_1.0.17 jsonlite_2.0.0 GlobalOptions_0.1.2
[21] BiocManager_1.30.26 ComplexHeatmap_2.20.0 purrr_1.1.0 crosstalk_1.2.2 scales_1.4.0 codetools_0.2-20 jquerylib_0.1.4 cli_3.6.5 rlang_1.1.6 crayon_1.5.3
[31] bit64_4.6.0-1 withr_3.0.2 cachem_1.1.0 yaml_2.3.10 tools_4.4.2 parallel_4.4.2 tzdb_0.5.0 dplyr_1.1.4 colorspace_2.1-1 DT_0.34.0
[41] forcats_1.0.0 GetoptLong_1.0.5 BiocGenerics_0.50.0 vctrs_0.6.5 R6_2.6.1 png_0.1-8 matrixStats_1.5.0 stats4_4.4.2 lifecycle_1.0.4 stringr_1.5.1
[51] htmlwidgets_1.6.4 bit_4.6.0 S4Vectors_0.42.1 IRanges_2.38.1 vroom_1.6.5 clue_0.3-66 cluster_2.1.8.1 pkgconfig_2.0.3 pillar_1.11.0 bslib_0.9.0
[61] gtable_0.3.6 glue_1.8.0 xfun_0.53 tibble_3.3.0 tidyselect_1.2.1 knitr_1.50 farver_2.1.2 rjson_0.2.23 htmltools_0.5.8.1 rmarkdown_2.29
[71] readr_2.1.5 compiler_4.4.2
LS0tCnRpdGxlOiAiRXhwbG9yZSBTQ2ltaWxhcml0eSBjZWxsIHR5cGUgYW5ub3RhdGlvbnMiCmF1dGhvcjogQWxseSBIYXdraW5zCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgY29kZV9mb2xkaW5nOiBoaWRlCi0tLQoKVGhpcyBub3RlYm9vayBzdW1tYXJpemVzIHRoZSBmaW5kaW5ncyBmcm9tIGFzc2lnbmluZyBjZWxsIHR5cGUgbGFiZWxzIHRvIGFsbCBTY1BDQSBzYW1wbGVzIHVzaW5nIGBTQ2ltaWxhcml0eWAuIAoKUnVubmluZyB0aGlzIG5vdGVib29rIHJlcXVpcmVzIHRoZSByZXN1bHRzIGZyb20gdGhlIGBjZWxsLXR5cGUtY29uc2Vuc3VzYCBhbmQgYGNlbGwtdHlwZS1zY2ltaWxhcml0eWAgbW9kdWxlIGluIGBPcGVuU2NQQ0EtbmZgIHRvIGJlIHNhdmVkIHRvIGBPcGVuU2NQQ0EtYW5hbHlzaXMvZGF0YS9jdXJyZW50L3Jlc3VsdHNgLiAKCmBgYHtyIHBhY2thZ2VzfQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogICMgbG9hZCByZXF1aXJlZCBwYWNrYWdlcwogIGxpYnJhcnkoZ2dwbG90MikKfSkKCiMgU2V0IGRlZmF1bHQgZ2dwbG90IHRoZW1lCnRoZW1lX3NldCgKICB0aGVtZV9jbGFzc2ljKCkKKQoKIyBEZWZpbmUgY29sb3IgcmFtcCBmb3Igc2hhcmVkIHVzZSBpbiB0aGUgaGVhdG1hcHMKaGVhdG1hcF9jb2xfZnVuIDwtIGNpcmNsaXplOjpjb2xvclJhbXAyKGMoMCwgMSksIGNvbG9ycyA9IGMoIndoaXRlIiwgImRhcmtzbGF0ZWJsdWUiKSkKIyBTZXQgaGVhdG1hcCBwYWRkaW5nIG9wdGlvbgpDb21wbGV4SGVhdG1hcDo6aHRfb3B0KFRJVExFX1BBRERJTkcgPSBncmlkOjp1bml0KDAuNiwgImluIikpCgojIHNldHRpbmdzCm9wdGlvbnMoCiAgZHBseXIuc3VtbWFyaXNlLmluZm9ybSA9IEZBTFNFLCAKICByZWFkci5zaG93X2NvbF90eXBlcyA9IEZBTFNFCikKCgpgYGAKCiMjIEZ1bmN0aW9ucwoKCmBgYHtyfQojIGZ1bmN0aW9uIHRvIHJlYWQgaW4gYm90aCBzY2ltaWxhcml0eSBhbmQgY29uc2Vuc3VzIHJlc3VsdHMgYW5kIGNvbWJpbmUgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lIApwcmVwX2RhdGEgPC0gZnVuY3Rpb24obGlicmFyeV9pZCwgc2NpbWlsYXJpdHlfZmlsZSwgY29uc2Vuc3VzX2ZpbGUpewogIAogIHNjaW1pbGFyaXR5X2RmIDwtIHJlYWRyOjpyZWFkX3RzdihzY2ltaWxhcml0eV9maWxlKQogIGNvbnNlbnN1c19kZiA8LSByZWFkcjo6cmVhZF90c3YoY29uc2Vuc3VzX2ZpbGUpCiAgCiAgYW5ub3RhdGlvbl9kZiA8LSBjb25zZW5zdXNfZGYgfD4gCiAgICBkcGx5cjo6bGVmdF9qb2luKHNjaW1pbGFyaXR5X2RmLCBieSA9IGMoImJhcmNvZGVzIiA9ICJiYXJjb2RlIikpIHw+IAogICAgZHBseXI6Om11dGF0ZSgKICAgICAgY2VsbF9pZCA9IGdsdWU6OmdsdWUoIntsaWJyYXJ5X2lkfS17YmFyY29kZXN9IiksIAogICAgICB0b3RhbF9jZWxsc19wZXJfbGlicmFyeSA9IGRwbHlyOjpuKCkKICAgICkKICAKICByZXR1cm4oYW5ub3RhdGlvbl9kZikKICAKfQpgYGAKCmBgYHtyfQojIENvcGllZCBmcm9tIHNjcGNhLW5mIHRlbXBsYXRlIHJlcG9ydCAKIycgRm9ybWF0IERUIGRhdGF0YWJsZXMKIycKIycgQHBhcmFtIGRmIERhdGEgZnJhbWUgdG8gZm9ybWF0CiMnCiMnIEByZXR1cm4gRFQgZGF0YXRhYmxlCmZvcm1hdF9kYXRhdGFibGUgPC0gZnVuY3Rpb24oZGYsIGNhcHRpb24pIHsKICBkZiB8PgogICAgRFQ6OmRhdGF0YWJsZSgKICAgICAgcm93bmFtZXMgPSBGQUxTRSwKICAgICAgZXh0ZW5zaW9ucyA9ICJTY3JvbGxlciIsCiAgICAgIGNhcHRpb24gPSBjYXB0aW9uLAogICAgICBvcHRpb25zID0gbGlzdCgKICAgICAgICBzY3JvbGxlciA9IFRSVUUsCiAgICAgICAgZGVmZXJSZW5kZXIgPSBUUlVFLAogICAgICAgIHNjcm9sbFggPSBUUlVFLAogICAgICAgIHNjcm9sbFkgPSA0MDAsCiAgICAgICAgc2Nyb2xsQ29sbGFwc2UgPSBUUlVFLAogICAgICAgIGxhbmd1YWdlID0gbGlzdChzZWFyY2ggPSAiRmlsdGVyOiIpLAogICAgICAgIGNvbHVtbkRlZnMgPSBsaXN0KGxpc3QoCiAgICAgICAgICAjIHJlbmRlciBtaXNzaW5nIHZhbHVlcyBhcyAiTkEiIHJhdGhlciB0aGFuIGJsYW5rcyBpbiB0aGUgdGFibGUKICAgICAgICAgIHRhcmdldHMgPSAiX2FsbCIsCiAgICAgICAgICByZW5kZXIgPSBEVDo6SlMoCiAgICAgICAgICAgICJmdW5jdGlvbihkYXRhLCB0eXBlLCByb3csIG1ldGEpIHsiLAogICAgICAgICAgICAicmV0dXJuIGRhdGEgPT09IG51bGwgPyAnTkEnIDogZGF0YTsiLAogICAgICAgICAgICAifSIKICAgICAgICAgICkKICAgICAgICApKQogICAgICApCiAgICApCn0KYGBgCgoKCiMjIERhdGEgc2V0dXAKCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeSwgZm91bmQgYnkgaXRzIChoaWRkZW4pIC5naXQgZGlyZWN0b3J5CnJlcG9zaXRvcnlfYmFzZSA8LSBycHJvanJvb3Q6OmZpbmRfcm9vdChycHJvanJvb3Q6OmlzX2dpdF9yb290KQojIHJlc3VsdHMgZGlyZWN0b3J5IHdpdGggY2VsbC10eXBlLWNvbnNlbnN1cyAKcmVzdWx0c19kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIsICJyZXN1bHRzIikKY29uc2Vuc3VzX3Jlc3VsdHNfZGlyIDwtIGZpbGUucGF0aChyZXN1bHRzX2RpciwgImNlbGwtdHlwZS1jb25zZW5zdXMiKQpzY2ltaWxhcml0eV9yZXN1bHRzX2RpciA8LSBmaWxlLnBhdGgocmVzdWx0c19kaXIsICJjZWxsLXR5cGUtc2NpbWlsYXJpdHkiKQoKIyBkaWFnbm9zZXMgdGFibGUgdXNlZCBmb3IgbGFiZWxpbmcgcGxvdHMgZnJvbSBjZWxsLXR5cGUtY29uc2Vuc3VzIG1vZHVsZQpkaWFnbm9zZXNfZmlsZSA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiYW5hbHlzZXMiLCAiY2VsbC10eXBlLWNvbnNlbnN1cyIsICJzYW1wbGUtaW5mbyIsICJwcm9qZWN0LWRpYWdub3Nlcy50c3YiKQpgYGAKCmBgYHtyfQojIHVzZSB0aGUgamFjY2FyZCBmdW5jdGlvbnMgZnJvbSBjZWxsLXR5cGUtbmV1cm9ibGFzdG9tYS0wNCBtb2R1bGUKamFjY2FyZF9mdW5jdGlvbnMgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImNlbGwtdHlwZS1uZXVyb2JsYXN0b21hLTA0IiwgInNjcmlwdHMiLCAidXRpbHMiLCAiamFjY2FyZC11dGlscy5SIikKc291cmNlKGphY2NhcmRfZnVuY3Rpb25zKQpgYGAKCgpgYGB7cn0KIyBsaXN0IGFsbCByZXN1bHRzIGZpbGVzIApjb25zZW5zdXNfcmVzdWx0c19maWxlcyA8LSBsaXN0LmZpbGVzKGNvbnNlbnN1c19yZXN1bHRzX2RpciwgcGF0dGVybiA9ICJfcHJvY2Vzc2VkX2NvbnNlbnN1cy1jZWxsLXR5cGVzXFwudHN2LlxcZ3okIiwgcmVjdXJzaXZlID0gVFJVRSwgZnVsbC5uYW1lcyA9IFRSVUUpCmNvbnNlbnN1c19maWxlc19kZiA8LSBkYXRhLmZyYW1lKAogIGxpYnJhcnlfaWQgPSBzdHJpbmdyOjp3b3JkKGJhc2VuYW1lKGNvbnNlbnN1c19yZXN1bHRzX2ZpbGVzKSwgMSwgc2VwID0gIl8iKSwKICBjb25zZW5zdXNfZmlsZSA9IGNvbnNlbnN1c19yZXN1bHRzX2ZpbGVzCikKCnNjaW1pbGFyaXR5X3Jlc3VsdHNfZmlsZXMgPC0gbGlzdC5maWxlcyhzY2ltaWxhcml0eV9yZXN1bHRzX2RpciwgcGF0dGVybiA9ICJfcHJvY2Vzc2VkX3NjaW1pbGFyaXR5LWNlbGx0eXBlLWFzc2lnbm1lbnRzXFwudHN2LlxcZ3okIiwgcmVjdXJzaXZlID0gVFJVRSwgZnVsbC5uYW1lcyA9IFRSVUUpCnNjaW1pbGFyaXR5X2ZpbGVzX2RmIDwtIGRhdGEuZnJhbWUoCiAgbGlicmFyeV9pZCA9IHN0cmluZ3I6OndvcmQoYmFzZW5hbWUoc2NpbWlsYXJpdHlfcmVzdWx0c19maWxlcyksIDEsIHNlcCA9ICJfIiksCiAgc2NpbWlsYXJpdHlfZmlsZSA9IHNjaW1pbGFyaXR5X3Jlc3VsdHNfZmlsZXMKKQoKYWxsX2ZpbGVzX2RmIDwtIHNjaW1pbGFyaXR5X2ZpbGVzX2RmIHw+IAogIGRwbHlyOjpsZWZ0X2pvaW4oY29uc2Vuc3VzX2ZpbGVzX2RmLCBieSA9ICJsaWJyYXJ5X2lkIikKCgojIGRlZmluZSBjZWxsIGxpbmUgcHJvamVjdHMgdG8gcmVtb3ZlCmNlbGxfbGluZV9wcm9qZWN0cyA8LSBjKCJTQ1BDUDAwMDAyMCIsICJTQ1BDUDAwMDAyNCIpCmBgYAoKYGBge3J9CiMgY2hlY2sgdGhhdCBldmVyeXRoaW5nIGhhcyBhIG1hdGNoaW5nIGNvbnNlbnN1cyBmaWxlIAojIHdlIGpvaW5lZCB0aGUgY29uc2Vuc3VzIGludG8gdGhlIHNjaW1pbGFyaXR5IHNvIG9ubHkgbGlicmFyaWVzIHdpdGggc2NpbWlsYXJpdHkgYXJlIGluY2x1ZGVkIAppZihzdW0oaXMubmEoYWxsX2ZpbGVzX2RmJGNvbnNlbnN1c19maWxlKSkgPiAwKXsKICBzdG9wKCJNaXNzaW5nIG1hdGNoaW5nIGNvbnNlbnN1cyBjZWxsIHR5cGUgZmlsZSBmb3IgYWxsIGxpYnJhcmllcyIpCn0KYGBgCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgcmVhZCBpbiBkaWFnbm9zZXMKZGlhZ25vc2VzX2RmIDwtIHJlYWRyOjpyZWFkX3RzdihkaWFnbm9zZXNfZmlsZSkKCiMgcmVhZCBpbiBhbGwgcmVzdWx0cyBhcyBhIHNpbmdsZSBkYXRhZnJhbWUKcmVzdWx0c19kZiA8LSBhbGxfZmlsZXNfZGYgfD4gCiAgcHVycnI6OnBtYXAocHJlcF9kYXRhKSB8PiAKICBkcGx5cjo6YmluZF9yb3dzKCkgfD4gCiAgIyByZW1vdmUgY2VsbCBsaW5lIHByb2plY3RzCiAgZHBseXI6OmZpbHRlcighcHJvamVjdF9pZCAlaW4lIGNlbGxfbGluZV9wcm9qZWN0cykgfD4gCiAgIyBhZGQgaW4gZGlhZ25vc2VzIAogIGRwbHlyOjpsZWZ0X2pvaW4oZGlhZ25vc2VzX2RmLCBieSA9ICJwcm9qZWN0X2lkIikgfD4gCiAgZHBseXI6Om11dGF0ZSgKICAgICMgY3JlYXRlIGEgbGFiZWwgZm9yIHBsb3R0aW5nCiAgICBwcm9qZWN0X2xhYmVsID0gZ2x1ZTo6Z2x1ZSgie3Byb2plY3RfaWR9OntkaWFnbm9zaXN9IikKICApCmBgYAoKIyMgYFNDaW1pbGFyaXR5YCByZXN1bHRzIAoKQmVsb3cgd2Ugd2lsbCBzdW1tYXJpemUgdGhlIHJlc3VsdHMgZnJvbSBgU0NpbWlsYXJpdHlgIG9uIGFsbCBTY1BDQSBwcm9qZWN0cy4gCgojIyMgVGFibGUgb2YgYWxsIGNlbGwgdHlwZXMgaW4gU2NQQ0EgCgpGaXJzdCwgd2UganVzdCBsb29rIGF0IHRoZSBjZWxsIHR5cGVzIGlkZW50aWZpZWQgYW5kIHNlZSBpZiBhbnkgb2YgdGhlIGNlbGxzIGFyZSBub3QgYXNzaWduZWQuIAoKYGBge3J9CnN1bShpcy5uYShyZXN1bHRzX2RmJHNjaW1pbGFyaXR5X2NlbGx0eXBlX2Fubm90YXRpb24pKQpgYGAKCkl0IGxvb2tzIGxpa2UgZXZlcnkgY2VsbCBnZXRzIGEgY2VsbCB0eXBlLCBzbyB0aGF0J3Mgc29tZXRoaW5nIHRvIGtlZXAgaW4gbWluZCB3aGVuIGxvb2tpbmcgYXQgdGhlc2UgcmVzdWx0cy4gCgpMZXQncyBzZWUgd2hhdCBjZWxsIHR5cGVzIGFyZSBwcmVzZW50ZWQgaW4gb3VyIGRhdGE/IAoKYGBge3J9CnJlc3VsdHNfZGYgfD4gCiAgZHBseXI6OnVuZ3JvdXAoKSB8PiAKICBkcGx5cjo6Y291bnQoc2NpbWlsYXJpdHlfY2VsbHR5cGVfYW5ub3RhdGlvbiwgbmFtZSA9ICJ0b3RhbF9jZWxscyIpIHw+IAogIGRwbHlyOjphcnJhbmdlKGRlc2ModG90YWxfY2VsbHMpKSB8PiAKICAgICAgZm9ybWF0X2RhdGF0YWJsZShjYXB0aW9uID0gIkFsbCBzYW1wbGVzIikKYGBgCgpTbyBpdCBsb29rcyBsaWtlIHdlIGhhdmUgYSBsb3Qgb2YgIm5hdGl2ZSBjZWxsIiBsYWJlbHMgd2hpY2ggcmVhbGx5IGp1c3QgbWVhbnMgdGhlIGNlbGwgaXMgYSBjZWxsIGFuZCBkaWRuJ3QgYWxpZ24gd2l0aCBhbnl0aGluZyBlbHNlIGluIHRoZSByZWZlcmVuY2UuIApUaGlzIGxhYmVsIGlzIHByb2JhYmx5IHNpbWlsYXIgdG8gb3VyICJvdGhlciIgbGFiZWwgaW4gYENlbGxBc3NpZ25gLiAKCkFsc28sIG1vc3Qgb2Ygb3VyIGNlbGxzIGFyZSBwcm9nZW5pdG9yIGNlbGxzIG9yIGhlbWF0b3BvaWV0aWMgc3RlbSBjZWxscywgd2hpY2ggZWl0aGVyIG1lYW5zIGEgbG90IG9mIHRoZSBjZWxscyBhcmUgdHVtb3IgY2VsbHMgYW5kIHNob3cgc3RlbSBhbmQgcHJvZ2VuaXRvciBsaWtlIHByb3BlcnRpZXMgb3IgdGhlc2UgYXJlIGZyb20gdGhlIGJsb29kIHR1bW9ycyB3aGljaCBtYWtlIHVwIGEgbGFyZ2UgcGFydCBvZiBvdXIgYXRsYXMuCkVpdGhlciB3YXksIHdlIGhhdmUgY2VsbCB0eXBlcyEgCgojIyMgVGFibGUgb2YgY2VsbCB0eXBlcyBwZXIgcHJvamVjdCAKClRoaXMgc2VjdGlvbiBzaG93cyBhIHRhYmxlIG9mIGFsbCBjZWxsIHR5cGVzIGlkZW50aWZpZWQgaW4gZWFjaCBwcm9qZWN0LiAKRWFjaCBwcm9qZWN0IHdpbGwgaGF2ZSBpdHMgb3duIHRhYmxlLiAKCmBgYHtyfQojIGdldCBhbGwgdGhlIHVuaXF1ZSBsYWJlbHMKcHJvamVjdF9sYWJlbHMgPC0gdW5pcXVlKHJlc3VsdHNfZGYkcHJvamVjdF9sYWJlbCkKCnRhYmxlcyA8LSBwcm9qZWN0X2xhYmVscyB8PiAKICBwdXJycjo6bWFwKFwobGFiZWwpIHsKICAgIAogICAgcmVzdWx0c19kZiB8PiAKICAgICAgZHBseXI6OmZpbHRlcihwcm9qZWN0X2xhYmVsID09IGxhYmVsKSB8PiAKICAgICAgZHBseXI6Omdyb3VwX2J5KHNjaW1pbGFyaXR5X2NlbGx0eXBlX2Fubm90YXRpb24pIHw+IAogICAgICBkcGx5cjo6c3VtbWFyaXNlKAogICAgICAgIHRvdGFsX2NlbGxzID0gZHBseXI6Om4oKQogICAgICApIHw+IAogICAgICBkcGx5cjo6YXJyYW5nZShkZXNjKHRvdGFsX2NlbGxzKSkgfD4gCiAgICAgIGZvcm1hdF9kYXRhdGFibGUoY2FwdGlvbiA9IGxhYmVsKQogICAgCiAgfSkKCmh0bWx0b29sczo6dGFnTGlzdCh0YWJsZXMpCmBgYAoKIyMjIENvbXBhcmlzb24gdG8gZXhpc3RpbmcgY29uc2Vuc3VzIGNlbGwgdHlwZXMgCgpJbiB0aGlzIHNlY3Rpb24sIHdlIGNvbXBhcmUgdGhlIHRvcCAxNSBgU0NpbWlsYXJpdHlgIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyB0byB0aGUgY29uc2Vuc3VzIGNlbGwgdHlwZXMgZGV0ZXJtaW5lZCB1c2luZyBvbmx5IGBDZWxsQXNzaWduYCBhbmQgYFNpbmdsZVJgIGFubm90YXRpb25zIGZvciBhbGwgbGlicmFyaWVzIGluIGEgc2luZ2xlIHByb2plY3QuIApUaGUgcm93cyBjb3JyZXNwb25kIHRvIGNvbnNlbnN1cyBjZWxsIHR5cGVzIGFuZCB0aGUgY29sdW1ucyBjb3JyZXNwb25kIHRvIGBTQ2ltaWxhcml0eWAgYW5ub3RhdGlvbnMuIApBbGwgb3RoZXIgY2VsbCB0eXBlcyBub3QgaW4gdGhlIHRvcCAxMCByZXByZXNlbnRlZCBjZWxsIHR5cGVzIGluIGEgcHJvamVjdCBhcmUgZ3JvdXBlZCBpbnRvIHRoZSAiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIiBjYXRlZ29yeS4gCgpgYGB7cn0KcmVzdWx0c19kZiA8LSByZXN1bHRzX2RmIHw+IAogICAgZHBseXI6Omdyb3VwX2J5KHByb2plY3RfaWQpIHw+IAogICAgZHBseXI6Om11dGF0ZSgKICAgICAgIyBnZXQgbW9zdCBmcmVxdWVudGx5IG9ic2VydmVkIGNlbGwgdHlwZXMgYWNyb3NzIGxpYnJhcmllcyBpbiB0aGF0IHByb2plY3QgCiAgICAgIHNjaW1pbGFyaXR5X2NlbGx0eXBlX2x1bXBlZCA9IGZvcmNhdHM6OmZjdF9sdW1wX24oc2NpbWlsYXJpdHlfY2VsbHR5cGVfYW5ub3RhdGlvbiwgMTUsIG90aGVyX2xldmVsID0gIkFsbCByZW1haW5pbmcgY2VsbCB0eXBlcyIsIHRpZXMubWV0aG9kID0gImZpcnN0IikgfD4gCiAgICAgICAgIyBzb3J0IGJ5IGZyZXF1ZW5jeSAKICAgICAgICBmb3JjYXRzOjpmY3RfaW5mcmVxKCkgfD4gCiAgICAgICAgIyBtYWtlIHN1cmUgYWxsIHJlbWFpbmluZyBhbmQgdW5rbm93biBhcmUgbGFzdCwgdXNlIHRoaXMgdG8gYXNzaWduIGNvbG9ycyBpbiBzcGVjaWZpYyBvcmRlcgogICAgICAgIGZvcmNhdHM6OmZjdF9yZWxldmVsKCJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLCBhZnRlciA9IEluZikgfD4gCiAgICAgICAgYXMuY2hhcmFjdGVyKCkKICAgICkKYGBgCgoKYGBge3IsIGZpZy5oZWlnaHQgPSAxMCwgZmlnLndpZHRoID0gMTB9CnByb2plY3RfbGFiZWxzIHw+IAogIHB1cnJyOjp3YWxrKFwobGFiZWwpewogICAgCiAgICByZXN1bHRzX2RmIHw+IAogICAgICBkcGx5cjo6ZmlsdGVyKHByb2plY3RfbGFiZWwgPT0gbGFiZWwpIHw+IAogICAgICBtYWtlX2phY2NhcmRfaGVhdG1hcCgKICAgICAgICBhbm5vdGF0aW9uX2NvbDEgPSAic2NpbWlsYXJpdHlfY2VsbHR5cGVfbHVtcGVkIiwKICAgICAgICBhbm5vdGF0aW9uX2NvbDIgPSAiY29uc2Vuc3VzX2Fubm90YXRpb24iLAogICAgICAgIGxhYmVsMSA9IGdsdWU6OmdsdWUoIntsYWJlbH0gXG5TQ2ltaWxhcml0eSIpLAogICAgICAgIGxhYmVsMiA9ICJDb25zZW5zdXMiCiAgICAgICkKICAgIAogIH0pCmBgYAoKQXMgZXhwZWN0ZWQgdGhlcmUgaXMgc29tZSBkaXJlY3QgYWdyZWVtZW50IG9yIHBsYWNlcyB3aGVyZSBtb3JlIGJyb2FkIGNvbnNlbnN1cyBjZWxsIHR5cGVzIGFyZSBzcGxpdCB1cCBhY3Jvc3MgbW9yZSBncmFudWxhciBjZWxsIHR5cGVzIGluIGBTQ2ltaWxhcml0eWAuIApBIGZldyBub3RhYmxlIGV4YW1wbGVzOiAKCi0gYFNDUENQMDAwMDAxYDogQ2VsbHMgbGFiZWxlZCBhcyBtYWNyb3BoYWdlcyBpbiBjb25zZW5zdXMgYXJlIHNwbGl0IGJldHdlZW4gZ2xpYWwgYW5kIG1pY3JvZ2xpYWwgY2VsbHMgaW4gYFNDaW1pbGFyaXR5YC4gCk5hdHVyYWwga2lsbGVyIGNlbGxzIG1hdGNoIHVwIGJldHdlZW4gY29uc2Vuc3VzIGFuZCBgU0NpbWlsYXJpdHlgLiAKLSBgU0NQQ1AwMDAwMDZgOiBFbmRvdGhlbGlhbCBjZWxscyBtYXRjaCB1cCBiZXR3ZWVuIGNvbnNlbnN1cyBhbmQgYFNDaW1pbGFyaXR5YC4gClRoZSB1bmtub3duIGNlbGxzIGluIGNvbnNlbnN1cyBjb3JyZXNwb25kIHRvIG1lc2VuY2h5bWFsIHN0ZW0gY2VsbHMgYW5kIGEgdmFyaWV0eSBvZiBraWRuZXkgY2VsbHMuIAotIGBTQ1BDUDAwMDAwN2A6IE1hdHVyZSBUIGNlbGwgaW4gY29uc2Vuc3VzIGlzIHNwbGl0IGJldHdlZW4gQ0Q0IGFuZCBDRDggVCBjZWxscyBpbiBgU0NpbWlsYXJpdHlgLiAKCldlIGFsc28gc2VlIHBsYWNlcyB3aGVyZSB0aGVyZSBpcyBub3QgYWdyZWVtZW50LCB3aGljaCBpcyB0byBiZSBleHBlY3RlZC4KCiMjIENvbmNsdXNpb25zCgpPZiB0aGUgMjAzIHBvc3NpYmxlIGNlbGwgdHlwZXMgaW4gYFNDaW1pbGFyaXR5YCwgMTkxIG9mIHRoZW0gYXJlIHJlcHJlc2VudGVkIGluIFNjUENBIHNhbXBsZXMuIApUaGVyZSBhcmUgcXVpdGUgYSBmZXcgc2NlbmFyaW9zIHdoZXJlIGNvbnNlbnN1cyBjZWxsIHR5cGVzICJhZ3JlZSIgd2l0aCBgU0NpbWlsYXJpdHlgLiAKSXQgd2lsbCBiZSBpbmZvcm1hdGl2ZSB0byBsb29rIGF0IHRoZSBuZXcgY29uc2Vuc3VzIGNlbGwgdHlwZXMgYW5kIGhvdyB0aGV5IGNoYW5nZSB3aXRoIHRoZSBhZGRpdGlvbiBvZiB0aGUgYFNDaW1pbGFyaXR5YCBhbm5vdGF0aW9ucywgYnV0IGluIGdlbmVyYWwgdGhlIGFkZGl0aW9uIG9mIGBTQ2ltaWxhcml0eWAgc2VlbXMgcXVpdGUgdXNlZnVsIQoKIyMgU2Vzc2lvbiBpbmZvIAoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAKCgo=